[Java] Getting Started JBot
小室です。 Joobyの続きも書かねばならないのですが、また面白いフレームワークを紹介されたので触ってみました。
はじめに
JBotはJava製のBotフレームワークです。現在はSlackをサポートしており、非常に簡単にSlackのBotプログラムが作成可能です。SpringBootベースですのでサーバー構築が非常に容易です。
Facebook and Twitter coming soon
とあるように、TwitterやFacebookなどのSNSをサポートする予定のようです。
Slack Developers Kitを見てみると他にも多数のフレームワークがあるようです。
サンプルを動かす
動作を確認するために手っ取り早くサンプルを確認してみます。
前提
- すでにSlackのTeamが一つあること
- Java8がインストール済み
JBotを取得する
gitからCloneしましょう。
$ git clone git@github.com:ramswaroop/jbot.git Cloning into 'jbot'... remote: Counting objects: 1421, done. remote: Compressing objects: 100% (16/16), done. remote: Total 1421 (delta 2), reused 0 (delta 0), pack-reused 1394 Receiving objects: 100% (1421/1421), 241.66 KiB | 371.00 KiB/s, done. Resolving deltas: 100% (582/582), done.
ディレクトリを確認してみます。 jbot
がフレームワーク、jbot-example
がサンプルコードです。
$ cd jbot jbot/ jbot-example/
今回は jbot-example
を利用します。
Botを作成する
Create a slack bot のリンクを実行します。 jbot-sample
というユーザー名で登録します。
Add Bot Integration
ボタンを押すとAPI Tokenが発行されます。不安な場合は、regenerateしておくと良いかもしれません。Botのアイコンや表示名などを設定する項目がありますが、今回はAPI Tokenのみ必要なので割愛します。
API Tokenをコピーしておき、Save Integration
をクリック。
設定ファイルを修正
resources/application.properties
を修正します。まずは単純な機能から。DirectMessageやMention、Conversationのみ実行できるように設定しましょう。
# botkit spring.jackson.property-naming-strategy=SNAKE_CASE # slack integrations rtmUrl=https://slack.com/api/rtm.start?token={token}&simple_latest&no_unreads slackBotToken=xoxb-50014402slackbottokenx29U9X1bQ slashCommandToken=X73Fv3Tokenx242CdpEq slackIncomingWebhookUrl=https://hooks.slack.com/services/T025DUwebhookurloOYvPiHL7y6
デフォルトでは上記のように記載されています。6行目を書き換えます。当然ながらこのslackBotToken
は無効なので、先程コピーして値に書き換えましょう。
今回はBotが単純な反応をさせだけなので、書き換えるのは slackBotToken 値の変更のみでOKです。
設定項目について
botkit
: プロパティの命名戦略なので特に変更する必要はないと思います。rtmUrl
: SlackのReal Time Messaging APIのエンドポイント設定です。こちらも特に触る必要はありません。 simple_latest や no_unreads のオプションの意味はSlackのRTM APIドキュメントに記載されています。slackBotToken
: SlackBotの起動に必要なAPI Tokenです。こちらは、SlackのBot Configurationから設定及び再生成が可能です。slashCommandToken
: Slackの/
で実行できるコマンドをカスタマイズすることができます。こちらは少々複雑なため、ローカルでのテストは難しそうです(Slashコマンド実行時のフックするURL等を指定する必要があるなど。)slackIncomingWebHookUrl
: Slackの特定のチャンネルにメッセージを配信することの出来るURL設定です。
実行
mavenコマンドで実行します。
$ mvn spring-boot:run [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building JBot Example 1.0.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] >>> spring-boot-maven-plugin:1.4.0.RELEASE:run (default-cli) > test-compile @ jbot-example >>> [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ jbot-example --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 1 resource [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ jbot-example --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ jbot-example --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory /Users/xxxxxx/Develop/spring/jbot/jbot-example/src/test/resources [INFO] [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ jbot-example --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] <<< spring-boot-maven-plugin:1.4.0.RELEASE:run (default-cli) < test-compile @ jbot-example <<< [INFO] [INFO] --- spring-boot-maven-plugin:1.4.0.RELEASE:run (default-cli) @ jbot-example --- . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.4.0.RELEASE) 2017-01-31 23:21:19.022 INFO 44078 --- [ main] example.jbot.JBotApplication : Starting JBotApplication on xxxxx.local with PID 44078 (/Users/xxxxxx/Develop/spring/jbot/jbot-example/target/classes started by xxxxxxxx in /Users/xxxxxxx/Develop/spring/jbot/jbot-example) (長いので省略) 2017-01-31 23:21:24.303 INFO 44078 --- [cTaskExecutor-1] o.s.w.s.c.WebSocketConnectionManager : Successfully connected 2017-01-31 23:21:24.399 INFO 44078 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2017-01-31 23:21:24.473 INFO 44078 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) 2017-01-31 23:21:24.479 INFO 44078 --- [ main] example.jbot.JBotApplication : Started JBotApplication in 6.681 seconds (JVM running for 12.893)
依存ライブラリ等がダウンロードされ実行されました。ちなみに無効なslackBotToken
を指定すると、以下のようなエラーが出ます。以下のエラーが出た場合は、ビルドのキャッシュクリーンやTokenをRegenerateするなどをすると、解消されることがあります。
2017-01-29 15:10:53.197 ERROR 41465 --- [ main] me.ramswaroop.jbot.core.slack.SlackDao : Error de-serializing RTM.start(): java.lang.NullPointerException: null at me.ramswaroop.jbot.core.slack.SlackDao$1.deserialize(SlackDao.java:61) [jbot-3.0.2.jar:3.0.2] at me.ramswaroop.jbot.core.slack.SlackDao$1.deserialize(SlackDao.java:55) [jbot-3.0.2.jar:3.0.2] at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789) [jackson-databind-2.8.1.jar:2.8.1] at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2913) [jackson-databind-2.8.1.jar:2.8.1] at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:225) [spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:213) [spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
SlackのRTM(Real Time Messaging API)への接続が失敗し、NullPoitnerException
が発生するようです(これ良いの・・・?)
SlackからBotに話しかける
DirectMessageで話しかけてみます。
Conversation(会話)を試してみましょう。private channelを作成し、BotをInviteしておきます。決まったメッセージを送ると会話をChainしてくれます。
コードを確認
今回確認した動作は example.jbot.slack.SlackBot.java
に記載してあります。
@Component public class SlackBot extends Bot { }
SpringのComponentScanで引っ掛けるために @Component
が付与されています。Bot
というabstractクラスを継承しています。
設定情報
@Value("${slackBotToken}") private String slackToken;
必要な設定情報は slackBotToken
だけです。
Direct Message, Direct Mention
Direct Messageや直接メンションをもらった場合の動作が定義してあります。
@Controller(events = {EventType.DIRECT_MENTION, EventType.DIRECT_MESSAGE}) public void onReceiveDM(WebSocketSession session, Event event) { reply(session, event, new Message("Hi, I am " + slackService.getCurrentUser().getName())); }
今回は固定メッセージで自分の名前を返しています。いわゆる村の入口にいる村人状態。
メッセージを解析する
正規表現パターンにマッチするメッセージがあれば自動的に反応します。
@Controller(events = EventType.MESSAGE, pattern = "^([a-z ]{2})(\\d+)([a-z ]{2})$") public void onReceiveMessage(WebSocketSession session, Event event, Matcher matcher) { reply(session, event, new Message("First group: " + matcher.group(0) + "\n" + "Second group: " + matcher.group(1) + "\n" + "Third group: " + matcher.group(2) + "\n" + "Fourth group: " + matcher.group(3))); }
Matcherの結果を利用することも出来るので、ユーザーが投稿したメッセージの内容もうまく利用することができそうです。
PINイベント
メッセージをPINすると反応します。
@Controller(events = EventType.PIN_ADDED) public void onPinAdded(WebSocketSession session, Event event) { reply(session, event, new Message("Thanks for the pin! You can find all pinned items under channel details.")); }
メッセージをPinすると、イベントに反応してメッセージが投稿されます。
ファイルのアップロードイベント
@Controller(events = EventType.FILE_SHARED) public void onFileShared(WebSocketSession session, Event event) { logger.info("File shared: {}", event); }
ファイルをシェアするとLoggerに書き込まれました。
2017-02-08 17:57:53.153 INFO 22028 --- [ient-SecureIO-1] example.jbot.slack.SlackBot : File shared: me.ramswaroop.jbot.core.slack.models.Event@58ec327
会話機能
会話はメソッドをチェインして呼ぶことができます。サンプルでの会話は、 「setup meeting」というメッセージを投稿すると開始されます。
会話の開始
@Controller(pattern = "(setup meeting)", next = "confirmTiming") public void setupMeeting(WebSocketSession session, Event event) { startConversation(event, "confirmTiming"); // start conversation reply(session, event, new Message("Cool! At what time (ex. 15:30) do you want me to set up the meeting?")); }
startConversation
を呼ぶと会話のチェインが開始されます。こちらのメソッドを抜けるとユーザーの入力を待ち、メッセージが投稿されたのを確認すると next
で指定された confirmTiming()
メソッドがコールされます。
会話のチェイン1(何もせず遷移)
@Controller(next = "askTimeForMeeting") public void confirmTiming(WebSocketSession session, Event event) { reply(session, event, new Message("Your meeting is set at " + event.getText() + ". Would you like to repeat it tomorrow?")); nextConversation(event); // jump to next question in conversation }
このメソッドでは入力された文字列をメッセージに埋め込み、表示します。特に受信したメッセージの検査はしていないので、実際は時刻表記の文字列ではなくても会話が進んでしまいます。nextConversation()
でアノテーションで指定した next
メソッドへ遷移します。
会話のチェイン2(yesかどうかをチェック)
@Controller(next = "askWhetherToRepeat") public void askTimeForMeeting(WebSocketSession session, Event event) { if (event.getText().contains("yes")) { reply(session, event, new Message("Okay. Would you like me to set a reminder for you?")); nextConversation(event); // jump to next question in conversation } else { reply(session, event, new Message("No problem. You can always schedule one with 'setup meeting' command.")); stopConversation(event); // stop conversation only if user says no } }
入力されたメッセージを確認し、 yes が含まれているかをチェックします。どこかにでも yes が入っていれば次の会話へ遷移します。それ以外は、会話を中止します。この場合、日本語で「はい」などを入力するとNGです。
- 変なメッセージになった例
会話の終了
@Controller public void askWhetherToRepeat(WebSocketSession session, Event event) { if (event.getText().contains("yes")) { reply(session, event, new Message("Great! I will remind you tomorrow before the meeting.")); } else { reply(session, event, new Message("Oh! my boss is smart enough to remind himself :)")); } stopConversation(event); // stop conversation }
最後は同じ構成なので省略します。yes かそれ以外でメッセージを変えているだけ。
会話機能は以下のように構成します。
- startConversation() で会話処理フロー開始
- nextConversation() でアノテーションで指定した次のメソッドへ繊維
- stopConversation() で会話処理フロー停止
まとめ
JBotはSpringBootベースのフレームワークなので、非常に簡単にBotサーバーが構築できました。Tokenを入力するだけなので非常に簡単です。工夫によっては様々なタスクを肩代わりさせることができそうです。
Javaでお手軽に書けるので他のWebサービスと連携させるなど工夫のしがいがありそうです。他にもSlashCommand(/
) や InCommingWebHookなどの機能も現時点では実装済みです。